/*   The MIT License
 *   
 *   Carina Engine
 *   Copyright (c) 2009 2010 2011 2012 Zdravko Velinov
 *   
 *   Permission is hereby granted, free of charge, to any person obtaining a copy
 *   of this software and associated documentation files (the "Software"), to deal
 *   in the Software without restriction, including without limitation the rights
 *   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *   copies of the Software, and to permit persons to whom the Software is
 *   furnished to do so, subject to the following conditions:
 *
 *   The above copyright notice and this permission notice shall be included in
 *   all copies or substantial portions of the Software.
 *
 *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 *   THE SOFTWARE.
 */

#include "carina/window-x11.hh"

#include "carina/common/exception.hh"
#include "carina/gl-library.hh"
#include "carina/gl-renderer.hh"

#include <X11/XKBlib.h>

#include <climits>

namespace Carina
{
const int event_mask = KeyPressMask | KeyReleaseMask | PointerMotionMask | StructureNotifyMask;

WindowManager::WindowManager(const string& _icon)
    :   m_Display(XOpenDisplay(0))
{
    if(!m_Display)
        THROW_EXCEPTION("the application has failed to open a X11 display");
}

WindowManager::~WindowManager()
{
    XCloseDisplay(m_Display);
}

WindowPtr WindowManager::createWindow(const RenderingLibraryPtr& lib, const WindowDescription& desc)
{
    return make_aligned_shared<Window>(*this, lib->createWindowImpl(), desc);
}

Window::Window(WindowManager& wmgr, const WindowImplPtr& wimpl, const WindowDescription& desc)
    :   m_WImpl(wimpl),
        m_CurCursorX(0),
        m_CurCursorY(0)
{
    assert(wimpl);
    m_WImpl->create(wmgr, desc.width, desc.height, desc.samples);
    m_WImpl->setTitle(desc.name);
    m_WImpl->show();
}

Window::~Window()
{
    m_WImpl->_destroyX11();
    m_WImpl->destroy();
}

void Window::swapBuffers()
{
    XEvent evt;
    WindowId wid = m_WImpl->getWindowId();
    DisplayId dpy = m_WImpl->getDisplayId();
    m_WImpl->swapBuffers();
    while(XCheckWindowEvent(dpy, wid, event_mask, &evt))
    {
        switch(evt.type)
        {
        case DestroyNotify:
        {
            RAISE_EVENT(this, QuitEvent, QuitEvent());
        } break;
        case ConfigureNotify:
        {
            RAISE_EVENT(this, ResizeWindowEvent, ResizeWindowEvent(evt.xconfigure.width, evt.xconfigure.height));
        } break;
        case KeyPress:
        {
            RAISE_EVENT(this, KeyboardEvent, KeyboardEvent(getKey(XkbKeycodeToKeysym(dpy, evt.xkey.keycode, 0, 0)), true));
        } break;
        case KeyRelease:
        {
            RAISE_EVENT(this, KeyboardEvent, KeyboardEvent(getKey(XkbKeycodeToKeysym(dpy, evt.xkey.keycode, 0, 0)), false));
        } break;
        case MotionNotify:
        {
            int x = evt.xmotion.x,
                y = evt.xmotion.y,
                xrel = x - m_CurCursorX,
                yrel = y - m_CurCursorY;
            m_CurCursorX = x;
            m_CurCursorY = y;
            RAISE_EVENT(this, MouseMotionEvent, MouseMotionEvent(x, y, xrel, yrel));
        } break;
        }
    }
}

WindowImpl::WindowImpl()
    :   m_Window(0),
        m_Display(0)
{
}

WindowImpl::~WindowImpl()
{
}

void WindowImpl::setTitle(const string& name)
{
    XStoreName(m_Display, m_Window, name.c_str());
}

void WindowImpl::show()
{
    XMapWindow(m_Display, m_Window);
}

void WindowImpl::_destroyX11()
{
    XDestroyWindow(m_Display, m_Window);
}

size_t WindowImpl::getWidth() const
{
    WindowId root;
    int x, y;
    unsigned width, height, border, depth;
    XGetGeometry(m_Display, m_Window, &root, &x, &y, &width, &height, &border, &depth);
    return width;
}

size_t WindowImpl::getHeight() const
{
    WindowId root;
    int x, y;
    unsigned width, height, border, depth;
    XGetGeometry(m_Display, m_Window, &root, &x, &y, &width, &height, &border, &depth);
    return height;
}

KeyboardKey Window::getKey(KeySym key)
{
    switch(key)
    {
    case XK_BackSpace: return CE_KEY_BACKSPACE;
    case XK_Tab: return CE_KEY_TAB;
    case XK_Return: return CE_KEY_ENTER;
    case XK_Control_L: return CE_KEY_LCTRL;
    case XK_Control_R: return CE_KEY_RCTRL;
    case XK_Shift_L: return CE_KEY_LSHIFT;
    case XK_Shift_R: return CE_KEY_RSHIFT;
    case XK_Alt_L: return CE_KEY_LALT;
    case XK_Alt_R: return CE_KEY_RALT;
    case XK_Super_L: return CE_KEY_LWIN;
    case XK_Super_R: return CE_KEY_RWIN;
    case XK_Pause: return CE_KEY_PAUSE;
    case XK_Caps_Lock: return CE_KEY_CAPSLOCK;
    case XK_Escape: return CE_KEY_ESCAPE;
    case XK_space: return CE_KEY_SPACE;
    case XK_Page_Up: return CE_KEY_PAGEUP;
    case XK_Page_Down: return CE_KEY_PAGEDOWN;
    case XK_End: return CE_KEY_END;
    case XK_Home: return CE_KEY_HOME;
    case XK_Left: return CE_KEY_LEFT;
    case XK_Right: return CE_KEY_RIGHT;
    case XK_Up: return CE_KEY_UP;
    case XK_Down: return CE_KEY_DOWN;
    case XK_Sys_Req: return CE_KEY_PRINTSCREEN;
    case XK_Insert: return CE_KEY_INSERT;
    case XK_Delete: return CE_KEY_DELETE;
    case '0': return CE_KEY_0;
    case '1': return CE_KEY_1;
    case '2': return CE_KEY_2;
    case '3': return CE_KEY_3;
    case '4': return CE_KEY_4;
    case '5': return CE_KEY_5;
    case '6': return CE_KEY_6;
    case '7': return CE_KEY_7;
    case '8': return CE_KEY_8;
    case '9': return CE_KEY_9;
    case 'a': return CE_KEY_A;
    case 'b': return CE_KEY_B;
    case 'c': return CE_KEY_C;
    case 'd': return CE_KEY_D;
    case 'e': return CE_KEY_E;
    case 'f': return CE_KEY_F;
    case 'g': return CE_KEY_G;
    case 'h': return CE_KEY_H;
    case 'i': return CE_KEY_I;
    case 'j': return CE_KEY_J;
    case 'k': return CE_KEY_K;
    case 'l': return CE_KEY_L;
    case 'm': return CE_KEY_M;
    case 'n': return CE_KEY_N;
    case 'o': return CE_KEY_O;
    case 'p': return CE_KEY_P;
    case 'q': return CE_KEY_Q;
    case 'r': return CE_KEY_R;
    case 's': return CE_KEY_S;
    case 't': return CE_KEY_T;
    case 'u': return CE_KEY_U;
    case 'v': return CE_KEY_V;
    case 'w': return CE_KEY_W;
    case 'x': return CE_KEY_X;
    case 'y': return CE_KEY_Y;
    case 'z': return CE_KEY_Z;
    case XK_KP_0: return CE_KEY_NUM0;
    case XK_KP_1: return CE_KEY_NUM1;
    case XK_KP_2: return CE_KEY_NUM2;
    case XK_KP_3: return CE_KEY_NUM3;
    case XK_KP_4: return CE_KEY_NUM4;
    case XK_KP_5: return CE_KEY_NUM5;
    case XK_KP_6: return CE_KEY_NUM6;
    case XK_KP_7: return CE_KEY_NUM7;
    case XK_KP_8: return CE_KEY_NUM8;
    case XK_KP_9: return CE_KEY_NUM9;
    case XK_F1: return CE_KEY_F1;
    case XK_F2: return CE_KEY_F2;
    case XK_F3: return CE_KEY_F3;
    case XK_F4: return CE_KEY_F4;
    case XK_F5: return CE_KEY_F5;
    case XK_F6: return CE_KEY_F6;
    case XK_F7: return CE_KEY_F7;
    case XK_F8: return CE_KEY_F8;
    case XK_F9: return CE_KEY_F9;
    case XK_F10: return CE_KEY_F10;
    case XK_F11: return CE_KEY_F11;
    case XK_F12: return CE_KEY_F12;
    case XK_Num_Lock: return CE_KEY_NUMLOCK;
    case XK_Scroll_Lock: return CE_KEY_SCROLLLOCK;
    }
    return CE_KEY_UNKNOWN;
}
}
